package serialize

import (
	"errors"
	"fmt"
	"io"
	"reflect"
	"sync"
	"sync/atomic"

	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"

	"github.com/tinylib/msgp/msgp"
)

var (
	ParamMatchError = errors.New("parameter mismatched")
)

var (
	bagPool = sync.Pool{}
)

func init() {
	bagPool.New = func() interface{} {
		return &Mpb{b: []byte{}}
	}
}

func GetBag() *Mpb {
	return bagPool.Get().(*Mpb)
}

//go:generate msgp
//msgp:ignore Mpb
type ISlice []interface{}

type Mpb struct {
	b    []byte
	Data ISlice

	// 用于未修改数据的直接发送，减少序列化次数
	changed bool
	origin  []byte
	alloc   int32
}

func (p *Mpb) Serializer() def.OptimizeSerializer {
	return p
}

//封包
func (p *Mpb) Encode(writer io.Writer) error {
	//enc := msgpack.NewEncoder(writer)
	//return enc.Encode(p)
	p.changed = true
	return msgp.Encode(writer, &p.Data)
}

//解包
func (p *Mpb) Decode(reader io.Reader) error {
	//dec := msgpack.NewDecoder(reader)
	//return dec.Decode(p)
	p.changed = true
	return msgp.Decode(reader, &p.Data)
}

func (p *Mpb) Marshal() ([]byte, error) {
	b, err := p.Data.MarshalMsg(p.b)
	if err != nil {
		return nil, err
	} else {
		p.changed = false
		p.origin = b
		return b, err
	}
}

func (p *Mpb) MarshalMsg([]byte) ([]byte, error) {
	return p.Marshal()
}

func (p *Mpb) Unmarshal(b []byte) error {
	//return msgpack.Unmarshal(b, p)
	_, err := p.Data.UnmarshalMsg(b)
	p.changed = false
	p.origin = b
	return err
}

func (p *Mpb) UnmarshalMsg(b []byte) ([]byte, error) {
	err := p.Unmarshal(b)
	return b, err
}

func (p *Mpb) Alloc() {
	atomic.AddInt32(&p.alloc, 1)
}
func (p *Mpb) Release() {
	if p.alloc > 0 {
		atomic.AddInt32(&p.alloc, -1)
		return
	}
	p.Data = nil
	p.changed = false
	p.origin = nil
	bagPool.Put(p)
}

func (p *Mpb) Copy(o def.OptimizeSerializer) {
	if b, ok := o.(*Mpb); ok {
		p.Data = b.Data
		p.changed = true
	}
}

func (p *Mpb) Set(d ...interface{}) {
	p.Data = d
	p.changed = true
}

func (p *Mpb) SetList(d []interface{}) {
	p.Data = d
	p.changed = true
}

func (p *Mpb) Len() int {
	return len(p.Data)
}

func (p *Mpb) IsNil(index int) bool {
	if len(p.Data) <= index {
		panic(ParamMatchError)
	}
	if p.Data[index] == nil {
		return true
	} else {
		return false
	}
}

func (p *Mpb) validIndex(index int) interface{} {
	if len(p.Data) <= index {
		panic(ParamMatchError)
	}
	return p.Data[index]
}

func (p *Mpb) Write(index int, d interface{}) {
	if len(p.Data) <= index {
		panic(ParamMatchError)
	}
	p.Data[index] = d
	p.changed = true
}

func (p *Mpb) Read(index int, value interface{}) error {
	v := p.validIndex(index)
	if value == nil {
		return nil
	}
	switch vv := value.(type) {
	case *string:
		*vv = utils.ToString(v)
	case *[]byte:
		*vv = utils.ToBytes(v)
	case *int:
		*vv = utils.ToInt(v)
	case *[]int:
		*vv = utils.ToIntSlice(v)
	case *int8:
		*vv = v.(int8)
	case *int16:
		*vv = v.(int16)
	case *int32:
		*vv = v.(int32)
	case *int64:
		*vv = v.(int64)
	case *uint:
		*vv = v.(uint)
	case *uint8:
		*vv = v.(uint8)
	case *uint16:
		*vv = v.(uint16)
	case *uint32:
		*vv = v.(uint32)
	case *uint64:
		*vv = v.(uint64)
	case *bool:
		*vv = v.(bool)
	case *[]bool:
		*vv = utils.ToBoolSlice(v)
	case *[][]byte:
		*vv = utils.ToBytesSlice(v)
	case *float32:
		*vv = v.(float32)
	case *float64:
		*vv = v.(float64)
	case *interface{}:
		*vv = v
	default:
		vr := reflect.ValueOf(value)
		if !vr.IsValid() {
			return errors.New("mpb Read: Decode(nil)")
		}
		if vr.Kind() != reflect.Ptr {
			return fmt.Errorf("mpb Read(nonsettable %T)", value)
		}
		vr = vr.Elem()
		if !vr.IsValid() {
			return fmt.Errorf("mpb Read(nonsettable %T)", value)
		}
		rv := reflect.ValueOf(v)
		if rv.Type() != vr.Type() {
			return fmt.Errorf("mpb Read(type nomatch %T, %T)", value, v)
		}
		vr.Set(reflect.ValueOf(v))
	}
	return nil
}

func (p *Mpb) ReadSerialize(index int, s def.Serializer) {
	field := p.validIndex(index)
	_ = s.Unmarshal(utils.ToBytes(field))
}

func (p *Mpb) ReadString(index int) string {
	field := p.validIndex(index)
	return utils.ToString(field)
}

func (p *Mpb) ReadInt16(index int) int16 {
	field := p.validIndex(index)
	if field == nil {
		return 0
	}
	return utils.ToInt16(field)
}

func (p *Mpb) ReadUint16(index int) uint16 {
	field := p.validIndex(index)
	if field == nil {
		return 0
	}
	return utils.ToUint16(field)
}

func (p *Mpb) ReadInt(index int) int {
	field := p.validIndex(index)
	if field == nil {
		return 0
	}
	return utils.ToInt(field)
}

func (p *Mpb) ReadInt32(index int) int32 {
	field := p.validIndex(index)
	if field == nil {
		return 0
	}
	return utils.ToInt32(field)
}

func (p *Mpb) ReadUint32(index int) uint32 {
	field := p.validIndex(index)
	if field == nil {
		return 0
	}
	return utils.ToUint32(field)
}

func (p *Mpb) ReadInt64(index int) int64 {
	field := p.validIndex(index)
	if field == nil {
		return 0
	}
	return utils.ToInt64(field)
}
func (p *Mpb) ReadUInt64(index int) uint64 {
	field := p.validIndex(index)
	if field == nil {
		return 0
	}
	return utils.ToUint64(field)
}
func (p *Mpb) ReadByte(index int) byte {
	field := p.validIndex(index)
	return utils.ToByte(field)
}

func (p *Mpb) ReadBytes(index int) []byte {
	field := p.validIndex(index)
	return utils.ToBytes(field)
}

func (p *Mpb) ReadBool(index int) bool {
	field := p.validIndex(index)
	return utils.ToBool(field)
}

func (p *Mpb) ReadInterface(index int) interface{} {
	return p.validIndex(index)
}

func (p *Mpb) ReadList(index int) []interface{} {
	return p.validIndex(index).([]interface{})
}

func (p *Mpb) ReadBag(index int) *Mpb {
	bag := GetBag()
	bag.SetList(p.validIndex(index).([]interface{}))
	return bag
}

func (p *Mpb) Append(d ...interface{}) {
	p.Data = append(d, p.Data...)
	p.changed = true
}

func (p *Mpb) Push(d ...interface{}) {
	p.Data = append(p.Data, d...)
	p.changed = true
}

func (p *Mpb) Pop() {
	p.Data = p.Data[0 : len(p.Data)-1]
	p.changed = true
}

func (p *Mpb) Shift() {
	p.Data = p.Data[1 : len(p.Data)-1]
	p.changed = true
}

func (p *Mpb) Replace(index int, d interface{}) {
	p.Data[index] = d
	p.changed = true
}

func (p *Mpb) Get() []interface{} {
	return p.Data[:]
}

func (p *Mpb) Bytes() (b []byte) {
	if p.changed {
		//p.b = p.b[0:0]
		b, _ = p.Data.MarshalMsg(p.b)
		p.origin = b
		p.changed = false
		return b
	} else {
		return p.origin
	}
}
